// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jaewon

#pragma once

#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>

#include "coin/feed/fastfeed/feed.pb.h"
#include "coin1/fastfeed/view/book_builder.h"
#include "coin1/fastfeed/view/trade_builder.h"
#include "coin1/fastfeed/view/view_collection.h"

namespace fastfeed {
namespace detail {

template <typename T>
struct CallbackInvoker {
  void operator()(const view::ViewBase* view) const { callback(static_cast<const T*>(view)); }
  std::function<void(const T*)> callback;
};  // struct CallbackInvoker

}  // namespace detail

class FeedBuilderBase {
 public:
  FeedBuilderBase() = default;
  explicit FeedBuilderBase(const std::string& sub_req);
  virtual ~FeedBuilderBase() = default;

  virtual const std::string& sub_req() const { return sub_req_; }
  virtual void set_from_timestamp(std::optional<int64_t> from_timestamp) = 0;

  virtual void SubscribeBook(
      const std::string& symbol,
      std::function<void(const view::BookBuilder*)> callback);

  virtual void SubscribeTrade(
      const std::string& symbol,
      std::function<void(const view::TradeBuilder*)> callback);

  virtual void Subscribe(
      proto::FeedType feed_type,
      const std::string& custom_feed_type,
      const std::string& symbol,
      const std::string& view_type,
      std::function<void(const view::ViewBase*)> callback) = 0;

  template <typename T>
  void Subscribe(
      proto::FeedType feed_type,
      const std::string& custom_feed_type,
      const std::string& symbol,
      const std::string& view_type,
      std::function<void(const T*)> callback) {
    Subscribe(
        feed_type,
        custom_feed_type,
        symbol,
        view_type,
        detail::CallbackInvoker<T> {callback});
  }

 private:
  std::string sub_req_;
};  // class FeedBuilderBase

class ProtoFeedBuilder : public FeedBuilderBase {
 public:
  ProtoFeedBuilder();
  explicit ProtoFeedBuilder(const std::string& sub_req);
  ~ProtoFeedBuilder() final = default;

  void set_from_timestamp(std::optional<int64_t> from_timestamp) final {
    from_timestamp_ = from_timestamp;
  }

  void UpdateBySerializedProto(std::string_view serialized);
  void UpdateByProto(const proto::Feed& feed);

  void Subscribe(
      proto::FeedType feed_type,
      const std::string& custom_feed_type,
      const std::string& symbol,
      const std::string& view_type,
      std::function<void(const view::ViewBase*)> callback) final;

  void EnableUserCallback() { invoke_user_callbacks_ = true; }
  void DisableUserCallback() { invoke_user_callbacks_ = false; }

 private:
  std::string ResolveViewType(
      proto::FeedType feed_type,
      const std::string& custom_feed_type,
      const std::string& symbol);

  proto::Feed feed_cache_;
  bool invoke_user_callbacks_ = true;
  bool invoke_user_callbacks_flag_ = true;

  view::ViewCollection view_collection_;
  std::optional<int64_t> from_timestamp_ = 0;
};  // class ProtoFeedBuilder

}  // namespace fastfeed
